#include "GamePlayScreen.h"

#include "b2DebugDraw.h"
#include "BackgroundLayer.h"
#include "GameObject.h"
#include "WaterFlow.h"
#include "Land.h"
#include "LandPhysic.h"
#include "Cloud.h"
#include "Spike.h"
#include "Item.h"
#include "Hero.h"
#include "HudLayer.h"

#include "BallTest.h"


GamePlayScreen::GamePlayScreen()
{
	GameLayer::GameLayer();
}

GamePlayScreen::~GamePlayScreen()
{
    this->clearGameLayer();
}

void GamePlayScreen::clearGameLayer()
{
    this->stopAllActions();
    this->unscheduleAllSelectors();

    if (createList) {
        createList->release();
        createList = NULL;
    }

    if (removeList) {
        removeList->release();
        removeList = NULL;
    }

    if (enemies) {
        enemies->release();
        enemies = NULL;
    }

    if (landLowestList) {
        landLowestList->release();
        landLowestList = NULL;
    }

    if (staticObjects) {
        staticObjects->release();
        staticObjects = NULL;
    }

    if (dynamicObjects) {
        dynamicObjects->release();
        dynamicObjects = NULL;
    }

    if (tiledMap) {
        tiledMap->release();
        tiledMap = NULL;
    }
}

void GamePlayScreen::initGameLayer()
{
    GameLayer::initGameLayer();

    isHost = true;
    tiledMap = NULL;
    landLowestList = new CCArray();
    staticObjects = new CCArray();
    dynamicObjects = new CCArray();
    enemies = new CCArray();
    createList = new CCArray();
    removeList = new CCArray();

    mGameData = &DataAccess::sharedDataAccess()->gameData;

    // load shape cache
    string objShapePath = "data/object_shape.xml";
    DataAccess::sharedDataAccess()->loadObjectShapePath(objShapePath);

    this->loadImage();
    this->loadMap();
    this->startGame();
}

void GamePlayScreen::loadGameLayer()
{

}

void GamePlayScreen::loadImage()
{
    CCSpriteBatchNode *commonBatchNode = CCSpriteBatchNode::create(COMMON_IMAGE, 1);
    CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(COMMON_PLIST, commonBatchNode->getTexture());
    this->addChild(commonBatchNode);

    CCString *filePath = CCString::createWithFormat(BACKGROUND_IMAGE, mGameData->selectedChapter);
    CCSpriteBatchNode *backgroundBatchNode = CCSpriteBatchNode::create(filePath->getCString(), 1);
    filePath = CCString::createWithFormat(BACKGROUND_PLIST, mGameData->selectedChapter);
    CCSpriteFrameCache::sharedSpriteFrameCache()
            ->addSpriteFramesWithFile(filePath->getCString(), backgroundBatchNode->getTexture());
    this->addChild(backgroundBatchNode);
}

void GamePlayScreen::loadMap()
{
    CCString *levelPath = CCString::createWithFormat(LEVEL_PATH_FORMAT,
                                                     mGameData->selectedChapter, mGameData->selectedLevel);    

    tiledMap = new CCTMXTiledMap();
    tiledMap->initWithTMXFile(levelPath->getCString());
    worldSize = CCSizeMake(tiledMap->getMapSize().width * tiledMap->getTileSize().width * gameScreenManager->scaleFactor,
                           tiledMap->getMapSize().height * tiledMap->getTileSize().height * gameScreenManager->scaleFactor);
    deathLine = DEATHLINE * gameScreenManager->scaleFactor;

    this->createb2World();
    this->createMap();
}

void GamePlayScreen::createb2World()
{
    world = new b2World(b2Vec2(0.0f, GRAVITY));
    world->SetAllowSleeping(true);
    world->SetContinuousPhysics(true);
    // contact listener
    world->SetContactListener(this);

    b2DebugDraw *debugDraw = new b2DebugDraw( PTM_RATIO );
     world->SetDebugDraw(debugDraw);

    uint32 flags = 0;
    flags += b2Draw::e_shapeBit;
    flags += b2Draw::e_jointBit;
    flags += b2Draw::e_aabbBit;
    flags += b2Draw::e_pairBit;
    flags += b2Draw::e_centerOfMassBit;
    debugDraw->SetFlags(flags);

    // Define the ground body.
    b2BodyDef groundBodyDef;
    groundBodyDef.position.Set(0, 0); // bottom-left corner

    // Call the body factory which allocates memory for the ground body
    // from a pool and creates the ground box shape (also from a pool).
    // The body is also added to the world.
    b2Body* groundBody = world->CreateBody(&groundBodyDef);

    // Define the ground box shape.
    b2EdgeShape groundBox;

    // bottom
    groundBox.Set(b2Vec2(0, 0), b2Vec2(worldSize.width/PTM_RATIO, 0));
    groundBody->CreateFixture(&groundBox,0);

    // top
    groundBox.Set(b2Vec2(0, worldSize.height/PTM_RATIO), b2Vec2(worldSize.width/PTM_RATIO, worldSize.height/PTM_RATIO));
    groundBody->CreateFixture(&groundBox,0);

    // left
    groundBox.Set(b2Vec2(0, worldSize.height/PTM_RATIO), b2Vec2(0,0));
    groundBody->CreateFixture(&groundBox,0);

    // right
    groundBox.Set(b2Vec2(worldSize.width/PTM_RATIO, worldSize.height/PTM_RATIO), b2Vec2(worldSize.width/PTM_RATIO,0));
    groundBody->CreateFixture(&groundBox,0);    
}

void GamePlayScreen::createMap()
{
    CCDictionary *objPropDict;
    CCArray *mapObjects;
    CCDictionary *objDict;
    CCObject *child;
    GameObject *gameObj;

    // load background
//    BackgroundLayer *background = new BackgroundLayer();
//    background->initBackground(this);
//    background->setPosition(ccp(0, 0));
//    this->addChild(background);
//    background->release();

    // load water flow
    WaterFlow *waterFlow = new WaterFlow();
    waterFlow->initGameObjectWith(this);
    this->addStaticObject(waterFlow, Z_WATER);

    // load land body
    objPropDict = tiledMap->objectGroupNamed(KEY_LANDBODY)->getProperties();
    mapObjects = tiledMap->objectGroupNamed(KEY_LANDBODY)->getObjects();
    LandPhysic *landPhysic;
    CCARRAY_FOREACH(mapObjects, child) {
        objDict = (CCDictionary*)child;
        landPhysic = new LandPhysic();
        landPhysic->initGameObjectWith(this, objDict, objPropDict);
        this->addStaticObject(landPhysic, Z_LAND);

        if (landPhysic->position.y + landPhysic->size.height/2.f < this->deathLine) {
            landLowestList->addObject(landPhysic);
        }
    }

    // load lands
    mapObjects = tiledMap->objectGroupNamed(KEY_LAND)->getObjects();
    CCARRAY_FOREACH(mapObjects, child) {
        objDict = (CCDictionary*)child;
        gameObj = new Land();
        gameObj->initGameObjectWith(this, objDict);
        this->addStaticObject(gameObj, Z_LAND);
    }    

    // load clouds
    mapObjects = tiledMap->objectGroupNamed(KEY_CLOUD)->getObjects();
    CCARRAY_FOREACH(mapObjects, child) {
        objDict = (CCDictionary*)child;
        gameObj = new Cloud();
        gameObj->initGameObjectWith(this, objDict);
        this->addDynamicObject(gameObj, Z_CLOUD);
    }

    // load spikes
    mapObjects = tiledMap->objectGroupNamed(KEY_SPIKE)->getObjects();
    CCARRAY_FOREACH(mapObjects, child) {
        objDict = (CCDictionary*)child;
        gameObj = new Spike();
        gameObj->initGameObjectWith(this, objDict);
        this->addDynamicObject(gameObj, Z_SPIKE);
    }

    // load items
    mapObjects = tiledMap->objectGroupNamed(KEY_ITEM)->getObjects();
    CCARRAY_FOREACH(mapObjects, child) {
        objDict = (CCDictionary*)child;
        gameObj = new Item();
        gameObj->initGameObjectWith(this, objDict);
        this->addDynamicObject(gameObj, Z_ITEM);
    }

    // load hero and enemies
    this->loadChatacter();

    // create camera
    mCamera = new GameCamera();
    //mCamera->initGameCamera(CCDirector::sharedDirector()->getWinSize(), this->boundingBox(), this);
    mCamera->initGameCamera(CCDirector::sharedDirector()->getWinSize(),
                            CCRectMake(0, 0, worldSize.width, worldSize.height), this);
    mCamera->setPosition(mHero->sprite->getPosition());
    mCamera->focusTo(mHero->sprite);
    this->addChild(mCamera);
    mCamera->release();

    // hud layer
    hudLayer = new HudLayer();
    hudLayer->initHudLayer(this);
    this->parentScene->addChild(hudLayer, Z_HUDLAYER);
    hudLayer->release();

    BallTest *ball = new BallTest();
    ball->initGameObjectWith(this, NULL);
    this->addDynamicObject(ball, 10);
}

void GamePlayScreen::startGame()
{
    gameState = GAME_STATE_RUNNING;
    this->scheduleUpdate();
}

void GamePlayScreen::addStaticObject(GameObject *_staticObject, int _z)
{
    if (_staticObject->sprite) {
        this->addChild(_staticObject->sprite, _z);
    }
    staticObjects->addObject(_staticObject);
    _staticObject->release();
}

void GamePlayScreen::addDynamicObject(GameObject *_gameObject, int _z)
{
    if (_gameObject->sprite) {
        this->addChild(_gameObject->sprite, _z);
    }
    dynamicObjects->addObject(_gameObject);
    _gameObject->release();
    CCLog("sprite retain: %d", _gameObject->sprite->retainCount());
}

void GamePlayScreen::addToCreateList(GameObject *_gameObj)
{
    if (!createList->containsObject(_gameObj)) {
        createList->addObject(_gameObj);
    }
}

void GamePlayScreen::addToRemoveList(GameObject *_gameObj)
{
    if (!removeList->containsObject(_gameObj)) {
        removeList->addObject(_gameObj);
    }
}

void GamePlayScreen::destroyBody(b2Body *_physicBody)
{
    b2JointEdge *jointEdge = _physicBody->GetJointList();
    b2JointEdge *curJoint;
    while (jointEdge) {
        curJoint = jointEdge;
        jointEdge = jointEdge->next;
        if (curJoint) {
            world->DestroyJoint(curJoint->joint);
        }
    }
    world->DestroyBody(_physicBody);
}

void GamePlayScreen::removeGameObject(GameObject *_gameObj)
{
    this->removeChild(_gameObj->sprite, true);
    this->dynamicObjects->removeObject(_gameObj, true);
}

void GamePlayScreen::update(float dt)
{
    if (gameState == GAME_STATE_RUNNING) {
        GameObject *gameObject;

        // remove objects
        remove_list_mutex.lock();
        for (unsigned int i = 0; i < removeList->count(); ++i) {
            gameObject = (GameObject*)removeList->objectAtIndex(i);
            gameObject->removeObject();
        }
        removeList->removeAllObjects();
        remove_list_mutex.unlock();

        // create objects
        create_list_mutex.lock();
        for (unsigned int i = 0; i < createList->count(); ++i) {
            gameObject = (GameObject*)createList->objectAtIndex(i);
            this->addDynamicObject(gameObject, Z_DOBJECT);
        }
        createList->removeAllObjects();
        create_list_mutex.unlock();

        // proccess objects
        dynamic_objects_mutex.lock();
        for (unsigned int i = 0; i < dynamicObjects->count(); ++i) {
            gameObject = (GameObject*)dynamicObjects->objectAtIndex(i);
            gameObject->update(dt);
        }
        dynamic_objects_mutex.unlock();

        world->Step(dt, 8, 1);

        mCamera->update(dt);
    }
}

void GamePlayScreen::BeginContact(b2Contact *contact)
{
    dynamic_objects_mutex.lock();

    PhysicObject *objA = (PhysicObject*)contact->GetFixtureA()->GetBody()->GetUserData();
    PhysicObject *objB = (PhysicObject*)contact->GetFixtureB()->GetBody()->GetUserData();

    if (objA != NULL && objB != NULL) {
        objA->beginContact(contact->GetFixtureA(), contact->GetFixtureB(), contact);
        objB->beginContact(contact->GetFixtureB(), contact->GetFixtureA(), contact);
    }

    dynamic_objects_mutex.unlock();
}

void GamePlayScreen::EndContact(b2Contact *contact)
{
    dynamic_objects_mutex.lock();

    PhysicObject *objA = (PhysicObject*)contact->GetFixtureA()->GetBody()->GetUserData();
    PhysicObject *objB = (PhysicObject*)contact->GetFixtureB()->GetBody()->GetUserData();

    if (objA != NULL && objB != NULL) {
        objA->endContact(contact->GetFixtureA(), contact->GetFixtureB(), contact);
        objB->endContact(contact->GetFixtureB(), contact->GetFixtureA(), contact);
    }

    dynamic_objects_mutex.unlock();
}

//----------------------------------------------------
void GamePlayScreen::draw()
{
    //
    // IMPORTANT:
    // This is only for debug purposes
    // It is recommend to disable it
    //
    CCLayer::draw();

    ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position );

    kmGLPushMatrix();

    world->DrawDebugData();

    kmGLPopMatrix();
}
